#!BPY
# -*- coding: cp1252 -*-
"""
Name: 'Blitz3D (.b3d)...'
Blender: 241
Group: 'Export'
Tip: 'Export b3d files for Blitz3d and Irrlicht'
"""

# blender2b3d.py version 0.01, 29/8/2006
# Exports animated characters
# from Blender to b3d (Blitz3d mesh format)
# By Luke Hoschke

# based on the md5 exporter by Thomas "der_ton" Hutter and 
# the Cal3D exporter written by Jean-Baptiste LAMY

# Ogre exporter written by Jens Hoffmann and Michael Reimpell
# helped get this working as the ogre exporter is very well documented 

# Thanks to vermeer from Irrlicht for helping out


# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# version history:
# 0.01  1/9/2006: started

#########################################################################################
# Code starts here.

import sys, os, os.path, struct, math, string


# if you get an error here, it might be
# because you don't have Python installed.
# go to www.python.org and get python 2.4 (not necessarily the latest!)

import Blender


from Blender import Mathutils
from Blender.Mathutils import *


import struct

# HACK -- it seems that some Blender versions don't define sys.argv,
# which may crash Python if a warning occurs.
if not hasattr(sys, "argv"): sys.argv = ["???"]

# Helps for having the model rotated right.
# Put in BASE_MATRIX your own rotation if you need some.

#BASE_MATRIX = None # matrix_rotate_x(math.pi / 2.0)
#BASE_MATRIX = matrix_rotate_x(math.pi / 2.0)

BASE_MATRIX = RotationMatrix(90, 4, 'x')

mirrorMatrix = Mathutils.Matrix([ 1, 0, 0, 0], \
                                [ 0,-1, 0, 0], \
                                [ 0, 0, 1, 0], \
                                [ 0, 0, 0, 0])

BASE_MATRIX *= mirrorMatrix

#BASE_MATRIX = RotationMatrix(0, 4, 'x')

def write_b3d_int(value):
    return struct.pack("<i", value)

def write_b3d_float(value):
   return struct.pack("<f", value)

def write_b3d_string(value):
    #new_value=""
    #for i in range(len(value)):
    #    if value[i]==chr(0):
    #        print "null found"
    #        new_value=new_value+" "
    #    else:
    #        new_value=new_value+value[i]

    binary_format = "<%ds" % (len(value)+1)
    return struct.pack(binary_format, value)

def write_b3d_header(name,value):
    return name+write_b3d_int(len(value))+value

def strip_path(value):
    # there has to be an easier way (I need to read the api)...
    if value=="":
        return ""
    i=len(value)
    new_value=""
    while i>=0:
        i=i-1
        if value[i]=="/" or value[i]=="\\":
            i=i+1
            while i<len(value):
                new_value=new_value+value[i]
                i=i+1
            return new_value
        #print "i is:",i
        #print value[i]
    return value

def write_chunk_anim(frames):
    chunk_buf=""
    chunk_buf=chunk_buf+write_b3d_int(0)
    chunk_buf=chunk_buf+write_b3d_int(frames)
    chunk_buf=chunk_buf+write_b3d_float(24)

    return write_b3d_header("ANIM",chunk_buf)

def write_chunk_bones(nodeMatrixInvert):
    chunk_buf=""

    for B3dbone in Bone_list:
        if not B3dbone.parent:
            chunk_buf=chunk_buf+write_chunk_bone_node(B3dbone,nodeMatrixInvert)
    return chunk_buf

def write_chunk_bone_node(B3dbone,nodeMatrixInvert):
    #global BASE_MATRIX

    chunk_buf=""

    # matrix = obj.getMatrix('worldspace')

    matrix = B3dbone.pmatrix

    if not B3dbone.parent:
        if nodeMatrixInvert:
            print "bone * invert"
            matrix=matrix

    chunk_buf=chunk_buf+write_b3d_string(B3dbone.name)

    # position
    translation=matrix.translationPart()
    chunk_buf=chunk_buf+write_b3d_float(translation[0]) # x
    chunk_buf=chunk_buf+write_b3d_float(translation[1]) # y
    chunk_buf=chunk_buf+write_b3d_float(translation[2]) # z
    # scale
    scale=matrix.scalePart()
    chunk_buf=chunk_buf+write_b3d_float(scale[0]) # x
    chunk_buf=chunk_buf+write_b3d_float(scale[1]) # y
    chunk_buf=chunk_buf+write_b3d_float(scale[2]) # z
    # rotation

    bquat = matrix.toQuat()
    bquat.normalize()
    qx = bquat.x
    qy = bquat.y
    qz = bquat.z

    chunk_buf=chunk_buf+write_b3d_float(-bquat.w) # w
    chunk_buf=chunk_buf+write_b3d_float(qx) # x
    chunk_buf=chunk_buf+write_b3d_float(qy) # y
    chunk_buf=chunk_buf+write_b3d_float(qz) # z


    chunk_buf=chunk_buf+write_chunk_bone_bone(B3dbone)

    chunk_buf=chunk_buf+write_chunk_keys(B3dbone)

    for tmp_B3dbone in Bone_list:
        if tmp_B3dbone.parent==B3dbone:
            chunk_buf=chunk_buf+write_chunk_bone_node(tmp_B3dbone,None)

    return write_b3d_header("NODE",chunk_buf)
    
def write_chunk_bone_bone(B3dbone):
    chunk_buf=""

    for i in range(len(B3dbone.vertices)):
        for uv_i in range( len( B3dbone.vertices[i].uv ) ):
            chunk_buf=chunk_buf+write_b3d_int(B3dbone.vertices[i].b3dLocalID+uv_i)
            chunk_buf=chunk_buf+write_b3d_float(B3dbone.verticesWeight[i])
    return write_b3d_header("BONE",chunk_buf)

def write_chunk_keys(B3dbone):
    chunk_buf=""
    
    chunk_buf=chunk_buf+write_b3d_int(5)

        #self.keydataFrame=[]
        #self.keydataLoc=[]
        #self.keydataRot=[]
        #self.keydataScale=[]

    for i in range(len(B3dbone.keydataFrame)):
        chunk_buf=chunk_buf+write_b3d_int(B3dbone.keydataFrame[i])
        matrix = B3dbone.keydataMatrix[i]
        #self.keydataLoc.append( Loc )
        #self.keydataRot.append( Rot )
        #self.keydataScale.append( Scale )
        # position
        translation=B3dbone.keydataLoc[i]
        chunk_buf=chunk_buf+write_b3d_float(translation[0]) # x
        chunk_buf=chunk_buf+write_b3d_float(translation[1]) # y
        chunk_buf=chunk_buf+write_b3d_float(translation[2]) # z
        # rotation
        bquat = B3dbone.keydataRot[i]
        bquat.normalize()
        qx = bquat.x
        qy = bquat.y
        qz = bquat.z
        #if bquat.w > 0:
        #    qx = -qx
        #    qy = -qy
        #    qz = -qz
        chunk_buf=chunk_buf+write_b3d_float(-bquat.w) # w
        chunk_buf=chunk_buf+write_b3d_float(qx) # x
        chunk_buf=chunk_buf+write_b3d_float(qy) # y
        chunk_buf=chunk_buf+write_b3d_float(qz) # z
    return write_b3d_header("KEYS",chunk_buf)

def write_chunk_node(mesh,rangestart,rangeend,arm_action):
    global ToggleStatic
    global BASE_MATRIX

    chunk_buf=""

    #matrix = obj.getMatrix('worldspace')
    #matrix = BASE_MATRIX
    matrix = Matrix()

    chunk_buf=chunk_buf+write_b3d_string("root")

    # position
    translation=matrix.translationPart()
    chunk_buf=chunk_buf+write_b3d_float(translation[0]) # x
    chunk_buf=chunk_buf+write_b3d_float(translation[1]) # y
    chunk_buf=chunk_buf+write_b3d_float(translation[2]) # z
    # scale
    scale=matrix.scalePart()
    chunk_buf=chunk_buf+write_b3d_float(scale[0]) # x
    chunk_buf=chunk_buf+write_b3d_float(scale[1]) # y
    chunk_buf=chunk_buf+write_b3d_float(scale[2]) # z
    # rotation

    bquat = matrix.toQuat()
    bquat.normalize()
    qx = bquat.x
    qy = bquat.y
    qz = bquat.z
    #if bquat.w > 0:
    #    qx = -qx
    #    qy = -qy
    #    qz = -qz

    chunk_buf=chunk_buf+write_b3d_float(-bquat.w) # w
    chunk_buf=chunk_buf+write_b3d_float(qx) # x
    chunk_buf=chunk_buf+write_b3d_float(qy) # y
    chunk_buf=chunk_buf+write_b3d_float(qz) # z

    # position
    #chunk_buf=chunk_buf+write_b3d_float(0) # x
    #chunk_buf=chunk_buf+write_b3d_float(0) # y
    #chunk_buf=chunk_buf+write_b3d_float(0) # z

    # scale
    #chunk_buf=chunk_buf+write_b3d_float(1) # x
    #chunk_buf=chunk_buf+write_b3d_float(1) # y
    #chunk_buf=chunk_buf+write_b3d_float(1) # z


    #chunk_buf=chunk_buf+write_b3d_float(1) # w
    #chunk_buf=chunk_buf+write_b3d_float(0) # x
    #chunk_buf=chunk_buf+write_b3d_float(0) # y
    #chunk_buf=chunk_buf+write_b3d_float(0) # z

    chunk_buf=chunk_buf+write_chunk_mesh()


    if ToggleStatic==0:
        if arm_action:
            chunk_buf=chunk_buf+write_chunk_anim(rangeend-rangestart)
            chunk_buf=chunk_buf+write_chunk_bones(None)
    
    return write_b3d_header("NODE",chunk_buf)


count=0
def write_chunk_mesh():
    global ToggleUV
    chunk_buf=""
    meshVertices=[]
    #self.pos    = pos
    #self.uv=[] # Cloned based on count
    #self.b3dGlobalID = 0
    #self.b3dLocalID = 0

    Offset=0

    for obj in Blender.Object.Get():
        data = obj.getData()
        if (type(data) is Blender.Types.NMeshType) and data.faces:
            scale=obj.getMatrix('worldspace').scalePart()
            print "mesh scale is ", scale[0], ", ", scale[1], ", ", scale[2]
            # create vertices and work out uv of vertices and faces....
            for face in data.faces[:]:
                if len(face.v)>2:
                    for vert in face.v:
                        meshVertices.append(Vertex(0,0,0))

            for face in data.faces[:]:
                if len(face.v)>2:
                    for vert in face.v:
                        b3dVert=meshVertices[vert.index+Offset]
                        if b3dVert.meshdata==0: #hack
                            meshMatrixLinked = obj.getMatrix('worldspace')
                            meshMatrix=Matrix(meshMatrixLinked[0],meshMatrixLinked[1],meshMatrixLinked[2],meshMatrixLinked[3])
                            #print "before:",vert.co[0],", ",vert.co[1],", ",vert.co[2]
                            vertexMatrix=TranslationMatrix(vert.co)
                            vertexMatrix= vertexMatrix * meshMatrix
                            vertexMatrix*=BASE_MATRIX
                            b3dVert.co=vertexMatrix.translationPart()
                            b3dVert.no=vert.no
                            #print "after:",b3dVert.co[0],", ",b3dVert.co[1],", ",b3dVert.co[2]
                            #b3dVert.co=vert.co
                            b3dVert.meshdata=data

                            influences=data.getVertexInfluences(vert.index)

                            total = 0.0
                            
                            for bone_name, weight in influences:
                                total += weight


                            for bone_name, weight in influences:
                                if total!=0.0:
                                    for b3dbone in Bone_list:
                                        if b3dbone.name==bone_name:
                                            if weight>0:
                                                b3dbone.add_vertex(b3dVert, weight / total )

                            if data.hasVertexUV() and not data.hasFaceUV():
                                b3dVert.uv.append(vert.uvco[0])
                            if not data.hasVertexUV() and not data.hasFaceUV():
                                b3dVert.uv.append([0.0,0.0])
            
            if data.hasFaceUV():
                for face in data.faces[:]:
                    if len(face.v)>2:
                        for i in range(len(face.v)):
                            vert=face.v[i]
                            uv_found=0
                            for uv in  meshVertices[vert.index+Offset].uv:
                                if face.uv[i]==uv:
                                    uv_found=1
                            if uv_found==0:
                                meshVertices[vert.index+Offset].uv.append(face.uv[i])

            Offset=Offset+len(data.verts)
    # mesh header...
    chunk_buf=chunk_buf+write_b3d_int(-1) # brush_id
    # write mesh...
    chunk_buf=chunk_buf+write_chunk_vrts(meshVertices)

    Offset=0
    for obj in Blender.Object.Get():
        data = obj.getData()
        if (type(data) is Blender.Types.NMeshType) and data.faces:

            matid=-1

            #if len(data.getMaterials())>0:
            #    if data.getMaterials()[0]:
            #        matname=data.getMaterials()[0].name
            #
            #        for tmpid in range(0,len(materialsID)):
            #            if materialsID[tmpid]==matname:
            #                matid=tmpid
            #                print "mesh ", data.name ," using ", matname

            chunk_buf=chunk_buf+write_chunk_tris(obj,data,matid,meshVertices,Offset)

            Offset=Offset+len(data.verts)
    
    return write_b3d_header("MESH",chunk_buf)

def write_chunk_vrts(meshVertices):
    global ToggleNormals
    chunk_buf=""
    # write vertices header...

    chunk_buf=chunk_buf+write_b3d_int(ToggleNormals) # flags
    chunk_buf=chunk_buf+write_b3d_int(1) # tex_coord_sets
    chunk_buf=chunk_buf+write_b3d_int(2) # tex_coord_set_size

    count=0

    for b3dvert in meshVertices:
        b3dvert.b3dLocalID=count # to help to build the trianges as cloning destroys ID/Index links
        for i in range( len( b3dvert.uv ) ): # clone vertices based on number of uvs
            # write vertices...
            chunk_buf=chunk_buf+write_b3d_float(b3dvert.co[0]) # x
            chunk_buf=chunk_buf+write_b3d_float(b3dvert.co[1]) # y
            chunk_buf=chunk_buf+write_b3d_float(b3dvert.co[2]) # z

            if ToggleNormals==1:
                chunk_buf=chunk_buf+write_b3d_float(b3dvert.no[0]) # n x
                chunk_buf=chunk_buf+write_b3d_float(b3dvert.no[1]) # n y
                chunk_buf=chunk_buf+write_b3d_float(b3dvert.no[2]) # n z

            chunk_buf=chunk_buf+write_b3d_float(b3dvert.uv[i][0]) # u
            chunk_buf=chunk_buf+write_b3d_float(1- b3dvert.uv[i][1]) # v

            count=count+1
    return write_b3d_header("VRTS",chunk_buf)


def write_chunk_tris(obj,data,matid,meshVertices,Offset):
    global ToggleUV

    # write triangles (note: ids are local within the mesh)

    chunk_buf=""
    master_chunk_buf=""

    #if matid<>-1: #if matid=-1, header is written later
    #    chunk_buf=chunk_buf+write_b3d_int(matid) # brush_id

    lastimage=None
    lastMaterialIndex=-1

    matid_written=0

    faces = data.faces
    for face in faces[:]:

        # work out material for this face, only change when material changes...

        if face.image:
            if lastimage<>face.image:
                lastMaterialIndex=-1
                tmp_matid=-1
                if face.image:
                      for tmpid in range(0,len(materialsID)):
                           if materialsID[tmpid]==face.image.name:
                               tmp_matid=tmpid
                               print "mesh ", data.name ," now using image ", materialsID[tmpid]
                if tmp_matid==-1:
                    print "mesh ", data.name ," now not using image "


                if matid_written==0:
                    matid_written=1
                else:
                    master_chunk_buf=master_chunk_buf+write_b3d_header("TRIS",chunk_buf)
                    chunk_buf=""

                chunk_buf=chunk_buf+write_b3d_int(tmp_matid) # brush_id

                lastimage=face.image

        else:

            if lastMaterialIndex<>face.materialIndex:

                lastimage=None

                tmp_matid=-1

                #print len(data.getMaterials())
                if len(data.getMaterials())>0:
                    if face.materialIndex>=0:
                        if data.getMaterials()[face.materialIndex]:
                            tmp_matname=data.getMaterials()[face.materialIndex].name
                            
                            for tmpid in range(0,len(materialsID)):
                                if materialsID[tmpid]==tmp_matname:
                                    tmp_matid=tmpid
                                    print "mesh ", data.name ," now using ", tmp_matname

                if tmp_matid==-1:
                    print "mesh ", data.name ," now not using material"


                if matid_written==0:
                    matid_written=1
                else:
                    master_chunk_buf=master_chunk_buf+write_b3d_header("TRIS",chunk_buf)
                    chunk_buf=""

                chunk_buf=chunk_buf+write_b3d_int(tmp_matid) # brush_id
                lastMaterialIndex=face.materialIndex

        if matid_written==0:
            matid_written=1
            chunk_buf=chunk_buf+write_b3d_int(-1) # brush_id

        # get vertex (out of its clones) matching the same uv as this face...      
        tmpIds=[-1,-1,-1,-1]

        if len(face.v)>2:
            if data.hasFaceUV() and ToggleUV==1:
                for i in range(len(face.v)):
                    for uv_i in range(len(meshVertices[face.v[i].index+Offset].uv)):
                        if meshVertices[face.v[i].index+Offset].uv[uv_i]==face.uv[i]:
                            tmpIds[i]=meshVertices[face.v[i].index+Offset].b3dLocalID+uv_i
            else:
                for i in range(len(face.v)):
                    tmpIds[i]=meshVertices[face.v[i].index+Offset].b3dLocalID
        #write face...
        if len(face.v)>2:
            if tmpIds[0]==-1:
                print "uv not found for face, mesh corrupted!"
                tmpIds[0]=0
            if tmpIds[1]==-1:
                print "uv not found for face, mesh corrupted!"
                tmpIds[1]=0
            if tmpIds[2]==-1:
                print "uv not found for face, mesh corrupted!"
                tmpIds[2]=0

            chunk_buf=chunk_buf+write_b3d_int(tmpIds[0]) # v0
            chunk_buf=chunk_buf+write_b3d_int(tmpIds[1]) # v1
            chunk_buf=chunk_buf+write_b3d_int(tmpIds[2]) # v2

            if len(face.v)==4: # 4 vertices in face
                if tmpIds[3]==-1:
                    print "uv not found for face, mesh corrupted!"
                    tmpIds[3]=0

                chunk_buf=chunk_buf+write_b3d_int(tmpIds[0]) # v0
                chunk_buf=chunk_buf+write_b3d_int(tmpIds[2]) # v1
                chunk_buf=chunk_buf+write_b3d_int(tmpIds[3]) # v2

    master_chunk_buf=master_chunk_buf+write_b3d_header("TRIS",chunk_buf)
    return master_chunk_buf

class Material:
  def __init__(self, map_filename = None):
    self.ambient_r  = 255
    self.ambient_g  = 255
    self.ambient_b  = 255
    self.ambient_a  = 255
    self.diffuse_r  = 255
    self.diffuse_g  = 255
    self.diffuse_b  = 255
    self.diffuse_a  = 255
    self.specular_r = 255
    self.specular_g = 255
    self.specular_b = 255
    self.specular_a = 255
    self.shininess = 1.0
    if map_filename: self.maps_filenames = [map_filename]
    else:            self.maps_filenames = []

    MATERIALS[map_filename] = self

    global NEXT_MATERIAL_ID
    self.id = NEXT_MATERIAL_ID
    NEXT_MATERIAL_ID += 1

  def to_b3dmesh(self):
    if self.maps_filenames:
      return self.maps_filenames[0]
    else:
      return ""

materialsID=[]
texturesID=[]
verticesID=[]

class Vertex:
  def __init__(self, meshdata, co, no):
    self.co    = co
    self.meshdata=meshdata
    self.uv=[] # Cloned based on count
    self.no = no
    self.b3dGlobalID = 0
    self.b3dLocalID = 0
    verticesID.append(self)

Bone_list = []

class B3dBones:
    def __init__(self, parent, name, mat, pmat, theboneobj):
        self.parent = parent # Bone
        self.name   = name   # string
        self.children = []   # list of Bone objects

        self.theboneobj = theboneobj #Blender.Armature.Bone

        self.vertices = []
        self.verticesWeight = []

        self.matrix = mat
        self.pmatrix = pmat # parentspace matrix

        #self.skeleton = skeleton

        self.keydataFrame=[]
        self.keydataMatrix=[]
        self.keydataLoc=[]
        self.keydataRot=[]
        self.keydataScale=[]

        if parent:
            parent.children.append(self)
        
        Bone_list.append(self)

    def add_vertex(self,vertex,weight):
        self.vertices.append(vertex)
        self.verticesWeight.append(weight)
        
    def addkeyforbone(self, frame, matrix,Loc,Rot,Scale):
        self.keydataFrame.append( frame )
        self.keydataMatrix.append( matrix )
        self.keydataLoc.append( Loc )
        self.keydataRot.append( Rot )
        self.keydataScale.append( Scale )
        return

def export(rangestart, rangeend, action_name):
  global ToggleStatic
  
  global BASE_MATRIX

  #Is this how to clear an array?
  for i in range(len(materialsID)):
      materialsID.pop()

  for i in range(len(texturesID)):
      texturesID.pop()

  for i in range(len(Bone_list)):
      Bone_list.pop()
  
  for i in range(len(verticesID)):
      verticesID.pop()

  # Get the scene

  #rotationMatrix = RotationMatrix(90,4,'x')
  #rotationMatrix *= RotationMatrix(0,4,'y')
  #rotationMatrix *= RotationMatrix(0,4,'z')
  #scaleMatrix = Matrix([1,0,0],[0,1,0],[0,0,1]).resize4x4()
  #BASE_MATRIX = rotationMatrix * scaleMatrix
  
  scene = Blender.Scene.getCurrent()
  context = scene.getRenderingContext()
  
  if ToggleStatic==0:
      action_dict = Blender.Armature.NLA.GetActions()
      if not action_dict.has_key(action_name):
        print "Specified action does not exist: %s " % (action_name)
        print "No b3d animation will be generated"
        arm_action = None
      else:
        arm_action = action_dict[action_name]
        ipomap = arm_action.getAllChannelIpos()


  if (b3dmesh_filename.val != ""):
    try:
      file = open(b3dmesh_filename.val, 'wb')
    except IOError, (errno, strerror):
      errmsg = "IOError #%s: %s" % (errno, strerror)

    if ToggleStatic==0:
        Blender.Set("curframe", 0)
        Blender.Window.Redraw()
        for obj in Blender.Object.Get():
            data = obj.getData()
            if type(data) is Blender.Types.ArmatureType:
              thearmature = obj
              armatureMatrix = obj.getMatrix('worldspace')
              
              armatureMatrix*= BASE_MATRIX.invert()

              def treat_bone(b, armatureMatrix, parent = None):
                if (parent and not b.parent.name==parent.name):
                  return #only catch direct children

                #mat = Blender.Mathutils.Matrix(b.matrix['ARMATURESPACE'])*armatureMatrix

                mat = Blender.Mathutils.Matrix(b.matrix['ARMATURESPACE'])

                #pmat is bone's matrix in parent space
                #pmat = Blender.Mathutils.Matrix(b.matrix['BONESPACE'])*matrix.toQuat().toMatrix() #god this api is ugly, bonespace, armaturespace matrices have different dimensions
                if (parent):
                    pmat = mat*Blender.Mathutils.Matrix(parent.theboneobj.matrix['ARMATURESPACE']).invert()
                else:
                    pmat = mat*armatureMatrix


                B3dbone = B3dBones(parent, b.name, mat, pmat, b)
               
                if (b.children):
                  for child in b.children: treat_bone(child,armatureMatrix, B3dbone)
                
              for b in data.bones.values():
                if (not b.parent):  # only treat root bones, treat_bone takes care of children
                  treat_bone(b, armatureMatrix)
              
              # Only one armature (we break here so this first armature in the scene is the only one we export)
              break

    if ToggleStatic==0:
        if arm_action:
            #animation = ANIMATIONS[arm_action.getName()] = MD5Animation(skeleton)
            arm_action.setActive(thearmature)
            armData = thearmature.getData()
            
            currenttime = rangestart # currenttime is blender's framenumber that we iterate over, getting also the frames between keyframes
                            # time is the "realtime", stored in keyframe objects and in animation.duration
            while currenttime <=rangeend:
              # context.currentFrame(int(currenttime))
              thearmature.evaluatePose( int(currenttime) )
              
              # Needed to update IPO's value, but probably not the best way for that...
              scene.makeCurrent()
              Blender.Set("curframe", int(currenttime))
              Blender.Window.Redraw()
              # Convert time units from Blender's frame (starting at 1) to seconds
              time = (currenttime - 1.0) / 24.0 #(assuming default 24fps for b3d anim)
              pose = thearmature.getPose()

              thearmatureMatrix= thearmature.getMatrix('worldspace')

              thearmatureMatrix*= BASE_MATRIX

              for bonename in armData.bones.keys():
                bone=pose.bones[bonename]
                posebonemat = Blender.Mathutils.Matrix(pose.bones[bonename].poseMatrix)


                B3dbone=None

                for tmp_B3dbone in Bone_list:
                    if tmp_B3dbone.name==bonename:
                        B3dbone=tmp_B3dbone

                if B3dbone:
                    if B3dbone.parent: # need parentspace-matrix
                      parentposemat = Blender.Mathutils.Matrix(pose.bones[B3dbone.parent.name].poseMatrix)
                      posebonemat = posebonemat*parentposemat.invert()
                    else:
                      posebonemat = posebonemat*thearmatureMatrix


                    Loc=posebonemat.translationPart()# - Vector(bone.loc)
                    Rot=posebonemat.rotationPart().toQuat()
                    Rot.normalize()
                    Scale=posebonemat.scalePart()



                    B3dbone.addkeyforbone(currenttime-rangestart, posebonemat, Loc, Rot, Scale)
              currenttime += 1


        Blender.Set("curframe", 0)
        Blender.Window.Redraw()
    
    # -------Start writing b3d------------

    #scene = Blender.Scene.getCurrent()
    #Blender.Set("curframe", 0)
    #Blender.Window.Redraw()
    
    buf=""
    
    buf=buf+write_b3d_int(1) # version, info says "major*100+minor" but for some reason exporter use 1?
    
    # write textures...
    
    chunk_buf=""
  
    for obj in Blender.Object.Get():
        data = obj.getData()
        if (type(data) is Blender.Types.NMeshType):
            for m in data.materials:
                if m:
                    for mtex in m.getTextures():
                        if mtex:
                            tex=mtex.tex
                            if tex:
                                teximage=tex.getImage()
                                if teximage:
                                    texturenamefound=0
                                    for name in texturesID:
                                        if name==tex.name:
                                            texturenamefound=1
                                    if texturenamefound==0:
                                            
                                        texturesID.append(tex.name)                                            
                                            

                                        print "Wrote texture ",tex.name
                                        
                                        chunk_buf=chunk_buf+write_b3d_string(strip_path(teximage.getFilename())) # texture file name
                                        chunk_buf=chunk_buf+write_b3d_int(1) # flags
                                        chunk_buf=chunk_buf+write_b3d_int(2) # blend
                                        chunk_buf=chunk_buf+write_b3d_float(0) # x_pos
                                        chunk_buf=chunk_buf+write_b3d_float(0) # y_pos
                                        chunk_buf=chunk_buf+write_b3d_float(1) # x_scale
                                        chunk_buf=chunk_buf+write_b3d_float(1) # y_scale
                                        chunk_buf=chunk_buf+write_b3d_float(0) # rotation
                                        
        if (type(data) is Blender.Types.NMeshType) and data.faces:
            for face in data.faces[:]:
                if face.image:
                    image=face.image
                    
                    texturenamefound=0
                    for name in texturesID:
                        if name==image.name:
                            texturenamefound=1
                    if texturenamefound==0:
                        texturesID.append(image.name)
                        print "Wrote texture based on face ",image.name
                        
                        chunk_buf=chunk_buf+write_b3d_string(strip_path(image.getFilename())) # texture file name
                        chunk_buf=chunk_buf+write_b3d_int(1) # flags
                        chunk_buf=chunk_buf+write_b3d_int(2) # blend
                        chunk_buf=chunk_buf+write_b3d_float(0) # x_pos
                        chunk_buf=chunk_buf+write_b3d_float(0) # y_pos
                        chunk_buf=chunk_buf+write_b3d_float(1) # x_scale
                        chunk_buf=chunk_buf+write_b3d_float(1) # y_scale
                        chunk_buf=chunk_buf+write_b3d_float(0) # rotation
                            

    if len(chunk_buf)>0:
        buf = buf + write_b3d_header("TEXS",chunk_buf)
    

    # write brushes...
    
    
    # create materials (brush) based on blenders
    
    for obj in Blender.Object.Get():
        data = obj.getData()
        if (type(data) is Blender.Types.NMeshType):
            for m in data.materials:
                if m:
                    materialnamefound=0
                    for name in materialsID:
                        if name==m.name:
                            materialnamefound=1
                    if materialnamefound==0:
                        
                        materialsID.append(m.name)

                        print "Wrote material ",m.name
                        chunk_buf=""
                        
                        texid=-1
                        
                        
                        if len(m.getTextures())>0:
                            if m.getTextures()[0]:
                                if m.getTextures()[0].tex:
                                    
                                    texname=m.getTextures()[0].tex.name

                                    for tmpid in range(0,len(texturesID)):
                                        if texturesID[tmpid]==texname:
                                            texid=tmpid
                        
                                
                        if texid<>-1:
                            chunk_buf=chunk_buf+write_b3d_int(1) # n_texs
                        else:
                            chunk_buf=chunk_buf+write_b3d_int(0) # n_texs
                        
                        chunk_buf=chunk_buf+write_b3d_string(m.name) # brush name
                        chunk_buf=chunk_buf+write_b3d_float(1) # m.getRGBCol()[0]) # red
                        chunk_buf=chunk_buf+write_b3d_float(1) # m.getRGBCol()[1]) # green
                        chunk_buf=chunk_buf+write_b3d_float(1) # m.getRGBCol()[2]) # blue
                        chunk_buf=chunk_buf+write_b3d_float(m.getAlpha()) # alpha
                        chunk_buf=chunk_buf+write_b3d_float(0) # shininess
                        chunk_buf=chunk_buf+write_b3d_int(1) # blend
                        chunk_buf=chunk_buf+write_b3d_int(0) # fx
                        if texid<>-1:
                            chunk_buf=chunk_buf+write_b3d_int(texid) # textures used in brush
                        buf = buf + write_b3d_header("BRUS",chunk_buf)

    
    # create extra materials (brush) for face textures with no materials
           
    for obj in Blender.Object.Get():
        data = obj.getData()
        if (type(data) is Blender.Types.NMeshType) and data.faces:
            for face in data.faces[:]:
                if face.image:
                    image=face.image

                    matid=-1
                    
                    
                    for tmpid in range(0,len(materialsID)):
                        if materialsID[tmpid]==image.name:
                            matid=tmpid

                    texid=-1
                    
                    for tmpid in range(0,len(texturesID)):
                        if texturesID[tmpid]==image.name:
                            texid=tmpid

                    if matid==-1 and texid<>-1:
                        materialsID.append(image.name)

                        print "Wrote material for texture image ",image.name
                        chunk_buf=""
                        chunk_buf=chunk_buf+write_b3d_int(1) # n_texs
                        chunk_buf=chunk_buf+write_b3d_string(image.name) # brush name
                        chunk_buf=chunk_buf+write_b3d_float(1) # red
                        chunk_buf=chunk_buf+write_b3d_float(1) # green
                        chunk_buf=chunk_buf+write_b3d_float(1) # blue
                        chunk_buf=chunk_buf+write_b3d_float(1) # alpha
                        chunk_buf=chunk_buf+write_b3d_float(0) # shininess
                        chunk_buf=chunk_buf+write_b3d_int(1) # blend
                        chunk_buf=chunk_buf+write_b3d_int(0) # fx
                        chunk_buf=chunk_buf+write_b3d_int(texid) # textures used in brush
                        buf = buf + write_b3d_header("BRUS",chunk_buf)

    # Nodes, bones, and meshes...
    
    chunk_buf=""
    
    #chunk_buf=chunk_buf+write_b3d_string("root") #blitz3D has problems with meshes have more than one root
    
    #chunk_buf=chunk_buf+write_b3d_float(0) # x
    #chunk_buf=chunk_buf+write_b3d_float(0) # y
    #chunk_buf=chunk_buf+write_b3d_float(0) # z
    
    #chunk_buf=chunk_buf+write_b3d_float(1) # x
    #chunk_buf=chunk_buf+write_b3d_float(1) # y
    #chunk_buf=chunk_buf+write_b3d_float(1) # z
    
    # rotation
    bquat = BASE_MATRIX.toQuat()
    bquat.normalize()
    qx = bquat.x
    qy = bquat.y
    qz = bquat.z
    #chunk_buf=chunk_buf+write_b3d_float(-bquat.w) # w
    #chunk_buf=chunk_buf+write_b3d_float(qx) # x
    #chunk_buf=chunk_buf+write_b3d_float(qy) # y
    #chunk_buf=chunk_buf+write_b3d_float(qz) # z
    
    if ToggleStatic==0:
        chunk_buf=chunk_buf+write_chunk_node(0,rangestart,rangeend,arm_action)
    else:
        chunk_buf=chunk_buf+write_chunk_node(0,rangestart,rangeend,0)#chunk_buf=chunk_buf+write_chunk_node(0,rangestart,rangeend,arm_action)

    buf = buf + chunk_buf
    #buf = buf + write_b3d_header("NODE",chunk_buf)

    # if obj.getParent()

    # buffer = buffer +  skeleton.to_b3dmesh(len(meshes[0].submeshes))
    # buffer = buffer + meshes[0].to_b3dmesh()
    
    # buf = write_b3d_header("BB3D",buf)

    file.write( ("BB3D"+write_b3d_int(len(buf))+buf) )

    
    file.close()
    print "saved mesh to " + b3dmesh_filename.val

  else:
    print "No b3dmesh file was generated."

##  # this piece of code might be useful for someone, so it is left here in comments
##  # this generates a b3danim file that simply contains the idle pose
##  try:
##    file = open("idlepose.b3danim"), 'w')
##  except IOError, (errno, strerror):
##    errmsg = "IOError #%s: %s" % (errno, strerror)
##  animation = skeleton.to_idleb3danim() # animation is a b3dAnimation object, not an Animation
##  buffer = animation.to_b3danim()
##  file.write(buffer)
##  file.close()

draw_busy_screen = 0
EVENT_NOEVENT = 1
EVENT_EXPORT = 2
EVENT_QUIT = 3
EVENT_MESHFILENAME = 4
EVENT_ANIMFILENAME = 5
EVENT_MESHFILENAME_STRINGBUTTON = 6
EVENT_ANIMFILENAME_STRINGBUTTON = 7
EVENT_CAMEXPORT = 8
EVENT_CAM_ANIMFILENAME = 10

EVENT_TOGGLENORMALS = 11
EVENT_TOGGLEUV = 12
EVENT_TOGGLESTATIC = 13

b3dmesh_filename = Blender.Draw.Create("")


ToggleNormals=0
ToggleUV=1
ToggleStatic=0



if len(Blender.Armature.NLA.GetActions().keys())>0:
    firstaction_name = Blender.Armature.NLA.GetActions().keys()[0]
else:
    firstaction_name = ""
export_animation = Blender.Draw.Create(firstaction_name)


scene = Blender.Scene.getCurrent()
context = scene.getRenderingContext()

startframe_slider = Blender.Draw.Create(context.startFrame())
endframe_slider = Blender.Draw.Create(context.endFrame())
framespersecond = context.framesPerSec()





######################################################
# Callbacks for Window functions
######################################################
def b3dmeshname_callback(filename):
  global b3dmesh_filename
  b3dmesh_filename.val=filename

######################################################
# GUI Functions
######################################################
def handle_event(evt, val):
  if evt == Blender.Draw.ESCKEY:
    Blender.Draw.Exit()
    return

def handle_button_event(evt):
  global EVENT_NOEVENT, EVENT_EXPORT, EVENT_QUIT, EVENT_MESHFILENAME, EVENT_MESHFILENAME_STRINGBUTTON
  global EVENT_TOGGLENORMALS,EVENT_TOGGLEUV,EVENT_TOGGLESTATIC
  global ToggleNormals, ToggleUV, ToggleStatic
  global draw_busy_screen, b3dmesh_filename
  global export_animation
  global startframe_slider, endframe_slider

  if evt == EVENT_EXPORT:
    if b3dmesh_filename.val == "": return

    if not b3dmesh_filename.val.endswith('.b3d'): b3dmesh_filename.val = '%s.b3d' % b3dmesh_filename.val
    
    draw_busy_screen = 1
    Blender.Draw.Draw()
    export(startframe_slider.val, endframe_slider.val, export_animation.val)
    draw_busy_screen = 0
    Blender.Draw.Redraw(1)
    return
  if evt == EVENT_QUIT:
    Blender.Draw.Exit()
  if evt == EVENT_MESHFILENAME:
    tmpMeshName = Blender.sys.makename(ext = ".b3d")
    Blender.Window.FileSelector(b3dmeshname_callback, "Select b3d mesh file...",tmpMeshName)
    Blender.Draw.Redraw(1)
  if evt == EVENT_TOGGLENORMALS:
    ToggleNormals=1-ToggleNormals
    Blender.Draw.Redraw(1)
    # print ToggleNormals
  if evt == EVENT_TOGGLEUV:
    ToggleUV=1-ToggleUV
    Blender.Draw.Redraw(1)
  if evt == EVENT_TOGGLESTATIC:
    ToggleStatic=1-ToggleStatic
    Blender.Draw.Redraw(1)

    
def show_gui():
  global EVENT_NOEVENT, EVENT_CAMEXPORT, EVENT_EXPORT, EVENT_QUIT, EVENT_MESHFILENAME, EVENT_ANIMFILENAME, EVENT_MESHFILENAME_STRINGBUTTON, EVENT_ANIMFILENAME_STRINGBUTTON
  global EVENT_TOGGLENORMALS,EVENT_TOGGLEUV,EVENT_TOGGLESTATIC
  global ToggleNormals, ToggleUV, ToggleStatic
  global draw_busy_screen, b3dmesh_filename, b3danim_filename, scale_slider
  global export_animation
  global EVENT_CAM_MESHFILENAME, EVENT_CAM_ANIMFILENAME
  global b3dcamanim_filename
  global startframe_slider, endframe_slider
  button_width = 240
  browsebutton_width = 60
  button_height = 25
  if draw_busy_screen == 1:
    Blender.BGL.glClearColor(0.3,0.3,0.3,1.0)
    Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
    Blender.BGL.glColor3f(1,1,1)
    Blender.BGL.glRasterPos2i(20,25)
    Blender.Draw.Text("Please wait while exporting...")
    return
  Blender.BGL.glClearColor(0.6,0.6,0.6,1.0)
  Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT)
  Blender.Draw.Button("Export", EVENT_EXPORT, 20, 2*button_height, button_width, button_height, "Export to B3D mesh")
  Blender.Draw.Button("Quit", EVENT_QUIT, 20, button_height, button_width, button_height, "Quit this script")
  Blender.Draw.Button("Browse...", EVENT_MESHFILENAME, 21+button_width-browsebutton_width, 4*button_height, browsebutton_width, button_height, "Specify mesh file")
  b3dmesh_filename = Blender.Draw.String("Mesh file:", EVENT_MESHFILENAME_STRINGBUTTON, 20, 4*button_height, button_width-browsebutton_width, button_height, b3dmesh_filename.val, 255, "Mesh file to generate")

  export_animation = Blender.Draw.String("Export Action:", EVENT_NOEVENT, 20, 5*button_height, button_width, button_height, export_animation.val, 255, "Specify the name of the Action that should be exported (IPO name prefix)")
  
  Blender.Draw.Toggle("Normals", EVENT_TOGGLENORMALS, 20, 7*button_height, 75, 25, ToggleNormals, "Export normals as in Blender")
  #Blender.Draw.Toggle("Multiple UVs", EVENT_TOGGLEUV, 100, 7*button_height, 100, 25, ToggleUV, "Clone Vertexes to allow multiple UV per vertex")
  Blender.Draw.Toggle("Static", EVENT_TOGGLESTATIC, 210, 7*button_height, 75, 25, ToggleStatic, "Recommended for static meshes, Objects do not get merged")
  
  
  startframe_slider = Blender.Draw.Slider("First frame:", EVENT_NOEVENT, 20, 11*button_height, button_width, button_height, startframe_slider.val, 1, 500, 0, "First frame of animation to export")
  endframe_slider = Blender.Draw.Slider("Last frame:", EVENT_NOEVENT, 20, 10*button_height, button_width, button_height, endframe_slider.val, 1, 500, 0, "The last frame of animation to export")

Blender.Draw.Register (show_gui, handle_event, handle_button_event)
